// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.openapi.roots.impl.indexing

import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.AdditionalLibraryRootsProvider
import com.intellij.openapi.roots.ModuleRootModificationUtil
import com.intellij.openapi.roots.OrderRootType
import com.intellij.openapi.roots.SyntheticLibrary
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.testFramework.RunsInEdt
import com.intellij.util.indexing.IndexableSetContributor
import org.junit.Test

@RunsInEdt
class IndexableFilesBeneathExcludedDirectoryTest : IndexableFilesBaseTest() {

  @Test
  fun `excluded files must not be indexed`() {
    projectModelRule.createJavaModule("moduleName") {
      content("contentRoot") {
        excluded("excluded") {
          file("ExcludedFile.java", "class ExcludedFile {}")
        }
      }
    }
    assertIndexableFiles()
  }

  @Test
  fun `source root beneath excluded directory must be indexed`() {
    lateinit var aJava: FileSpec
    projectModelRule.createJavaModule("moduleName") {
      content("contentRoot") {
        excluded("excluded") {
          source("sources") {
            aJava = file("A.java", "class A {}")
          }
        }
      }
    }
    assertIndexableFiles(aJava.file)
  }

  @Test
  fun `files of a Library residing beneath module excluded directory must be indexed`() {
    lateinit var libraryRoot: DirectorySpec
    lateinit var libraryClass: FileSpec

    val module = projectModelRule.createJavaModule("moduleName") {
      content("contentRoot") {
        excluded("excluded") {
          // Must be indexed despite being excluded by module.
          libraryRoot = dir("library") {
            libraryClass = file("LibraryClass.java", "class LibraryClass {}")
          }
        }
      }
    }
    projectModelRule.addModuleLevelLibrary(module, "libraryName") { model ->
      model.addRoot(libraryRoot.file, OrderRootType.CLASSES)
    }
    assertIndexableFiles(libraryClass.file)
  }

  @Test
  fun `files of an SDK residing beneath module excluded directory must be indexed`() {
    lateinit var sdkRoot: DirectorySpec
    lateinit var sdkClass: FileSpec

    val module = projectModelRule.createJavaModule("moduleName") {
      content("contentRoot") {
        excluded("excluded") {
          // Must be indexed despite being excluded by module.
          sdkRoot = dir("sdk") {
            sdkClass = file("SdkClass.java", "class SdkClass {}")
          }
        }
      }
    }

    val sdk = projectModelRule.addSdk(projectModelRule.createSdk("sdkName")) { sdkModificator ->
      sdkModificator.addRoot(sdkRoot.file, OrderRootType.CLASSES)
    }
    ModuleRootModificationUtil.setModuleSdk(module, sdk)

    assertIndexableFiles(sdkClass.file)
  }

  // Roots provided by AdditionalLibraryRootsProvider are considered library source roots,
  // and they must be indexed even if they reside beneath excluded directories.
  @Test
  fun `files of AdditionalLibraryRootsProvider residing beneath module excluded directory must be indexed`() {
    lateinit var targetSource: FileSpec
    lateinit var targetSources: DirectorySpec

    lateinit var targetBinary: FileSpec
    lateinit var targetBinaries: DirectorySpec

    projectModelRule.createJavaModule("moduleName") {
      content("contentRoot") {
        excluded("excluded") {
          targetSources = moduleDir("sources") {
            targetSource = file("TargetSource.java", "class TargetSource {}")
          }
          targetBinaries = moduleDir("binaries") {
            targetBinary = file("TargetBinary.java", "class TargetBinary {}")
          }
        }
      }
    }

    val additionalLibraryRootsProvider = object : AdditionalLibraryRootsProvider() {
      override fun getAdditionalProjectLibraries(project: Project) = listOf(
        SyntheticLibrary.newImmutableLibrary(
          listOf(targetSources.file),
          listOf(targetBinaries.file),
          emptySet(),
          null
        )
      )
    }

    maskAdditionalLibraryRootsProviders(additionalLibraryRootsProvider)
    assertIndexableFiles(targetSource.file, targetBinary.file)
  }

  @Test
  fun `files of IndexableSetContributor residing beneath module excluded directory must not be indexed`() {
    lateinit var additionalRoots: DirectorySpec
    lateinit var additionalProjectRoots: DirectorySpec

    projectModelRule.createJavaModule("moduleName") {
      // Must not be indexed despite being provided by IndexableSetContributor.
      content("contentRoot") {
        excluded("excluded") {
          additionalProjectRoots = dir("additionalProjectRoots") {
            file("ExcludedFile.java", "class ExcludedFile {}")
          }
          additionalRoots = dir("additionalRoots") {
            file("ExcludedFile.java", "class ExcludedFile {}")
          }
        }
      }
    }

    val contributor = object : IndexableSetContributor() {
      override fun getAdditionalProjectRootsToIndex(project: Project): Set<VirtualFile> =
        setOf(additionalProjectRoots.file)

      override fun getAdditionalRootsToIndex(): Set<VirtualFile> =
        setOf(additionalRoots.file)
    }
    maskIndexableSetContributors(contributor)
    assertIndexableFiles()
  }

}